package com.yifeng.repo.endpoint.security.auth.impl;

import com.gomcarter.frameworks.base.exception.CustomException;
import com.google.common.base.Strings;
import com.nimbusds.jose.jwk.KeyType;
import com.yifeng.repo.base.utils.codec.Base64Helper;
import com.yifeng.repo.base.utils.converter.JacksonHelper;
import com.yifeng.repo.endpoint.security.auth.SecurityChecker;
import com.yifeng.repo.endpoint.security.console.api.BusinessService;
import com.yifeng.repo.endpoint.security.context.constant.SecurityConstant;
import com.yifeng.repo.endpoint.security.console.api.dto.AccountDto;
import com.yifeng.repo.endpoint.security.console.api.dto.AccountPermissionDto;
import com.yifeng.repo.endpoint.security.console.proxy.BusinessServiceProxy;
import com.yifeng.repo.endpoint.security.redis.worker.RedisCacheWorker;
import com.yifeng.repo.endpoint.security.utils.JwsHelper;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.security.interfaces.ECPublicKey;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * Created by daibing on 2023/8/24.
 */
@Slf4j
public class BusinessSecurityChecker implements SecurityChecker {
    private final ConcurrentSkipListSet<ECPublicKey> ecPublicKeySet = new ConcurrentSkipListSet<>();
    private final AtomicLong lastRefreshTime = new AtomicLong(0L);
    private BusinessService businessService;
    private RedisCacheWorker redisCacheWorker;
    private String redisKeyPrefix;

    @Override
    public String consoleType() {
        return SecurityConstant.BUSINESS;
    }

    @Override
    public void init(String url, String accessToken, int clientTimeoutMillis, RedisCacheWorker redisCacheWorker, String redisKeyPrefix) {
        this.businessService = new BusinessServiceProxy(url, accessToken, clientTimeoutMillis);
        this.redisCacheWorker = redisCacheWorker;
        this.redisKeyPrefix = redisKeyPrefix;
    }

    @Override
    public String checkToken(ServletRequest request) {
        String token = request.getParameter(SecurityConstant.TOKEN);
        if (!Strings.isNullOrEmpty(token)) {
            return token;
        }
        return ((HttpServletRequest) request).getHeader(SecurityConstant.TOKEN);
    }

    @Override
    public AccountDto checkAccount(ServletRequest request, String token) {
        // 1. 检查请求不带token：如果有账号信息就转换出账号信息，否则直接返回null
        if (Strings.isNullOrEmpty(token)) {
            String account = ((HttpServletRequest) request).getHeader(SecurityConstant.ACCOUNT);
            return Strings.isNullOrEmpty(account) ? null : JacksonHelper.toObj(account, AccountDto.class);
        }

        // 2. 检查请求带token：就必须解密token，先获取公钥（1小时刷新一次）
        if (lastRefreshTime.get() + 60 * 60 * 1000 <= System.currentTimeMillis()) {
            synchronized (ecPublicKeySet) {
                if (lastRefreshTime.get() + 60 * 60 * 1000 <= System.currentTimeMillis()) {
                    List<ECPublicKey> publicKeyList = businessService.listPublicKey()
                            .stream()
                            .map(s -> (ECPublicKey) JwsHelper.parsePublicKey(KeyType.EC, s))
                            .collect(Collectors.toList());
                    ecPublicKeySet.clear();
                    ecPublicKeySet.addAll(publicKeyList);
                    lastRefreshTime.set(System.currentTimeMillis());
                }
            }
        }

        // 3. 开始解密token
        for (ECPublicKey ecPublicKey : ecPublicKeySet) {
            try {
                String account = JwsHelper.checkToken(token, ecPublicKey);
                if (!Strings.isNullOrEmpty(account)) {
                    return JacksonHelper.toObj(account, AccountDto.class);
                }
            } catch (Throwable t) {
                log.info("解密失败：token={}, publicKey={}", token, new String(Base64Helper.encodeBase64(ecPublicKey.getEncoded())));
            }
        }
        throw new CustomException("Token解密失败：" + token);
    }

    @Override
    public AccountPermissionDto checkPermission(String code) {
        // 1. 从redis拉取账号权限
        String permission = redisCacheWorker.get(redisKeyPrefix + code);
        if (!Strings.isNullOrEmpty(permission)) {
            return JacksonHelper.toObj(permission, AccountPermissionDto.class);
        }

        // 2. 从账号中心拉取账号权限，并且写入redis  TODO: 问题是写入redis的缓存什么时候过期？如何快速更新菜单，重新登录换token后怎么清除应用自己的redis缓存
        AccountPermissionDto accountPermissionDto = businessService.getPermission(code);
        if (accountPermissionDto != null) {
            redisCacheWorker.put(redisKeyPrefix + code, 7 * 24 * 60 * 60, JacksonHelper.toJson(accountPermissionDto));
        }
        return accountPermissionDto;
    }

}
